Allow registering actions per-class
authorMatthias Clasen <mclasen@redhat.com>
Fri, 14 Jun 2019 12:12:10 +0000 (12:12 +0000)
committerMatthias Clasen <mclasen@redhat.com>
Tue, 18 Jun 2019 18:47:33 +0000 (14:47 -0400)
Add a facility to register and install actions
at class init time. The intended use for these
actions is for

a) context and other model-based menus
b) key bindings

Most of these actions are going to be stateless,
so add separate apis for the simple and stateful
cases.

We avoid creating an action group for these by
teaching the action muxer about these actions.
The action muxer also maintains the enabled
state for these actions.

docs/reference/gtk/gtk4-sections.txt
gtk/gtkactionmuxer.c
gtk/gtkactionmuxerprivate.h
gtk/gtkapplication.c
gtk/gtkwidget.c
gtk/gtkwidget.h

index 786bcde36189e822d5bd0e7ea3a49e43286352b9..a7d0d1578733d86ed4c8a5025c4e8fdcf6a40ea7 100644 (file)
@@ -4549,10 +4549,6 @@ gtk_widget_get_opacity
 gtk_widget_set_opacity
 gtk_widget_get_overflow
 gtk_widget_set_overflow
-gtk_widget_insert_action_group
-gtk_widget_list_action_prefixes
-gtk_widget_activate_action
-gtk_widget_activate_default
 gtk_widget_measure
 gtk_widget_snapshot_child
 gtk_widget_get_next_sibling
@@ -4629,6 +4625,18 @@ gtk_widget_class_set_connect_func
 gtk_widget_observe_children
 gtk_widget_observe_controllers
 
+<SUBSECTION Actions>
+gtk_widget_insert_action_group
+gtk_widget_activate_action
+gtk_widget_activate_default
+GtkWidgetActionActivateFunc
+GtkWidgetActionSetStateFunc
+GtkWidgetActionGetStateFunc
+gtk_widget_class_install_action
+gtk_widget_class_install_stateful_action
+gtk_widget_action_enabled_changed
+gtk_widget_action_state_changed
+
 <SUBSECTION Standard>
 GTK_WIDGET
 GTK_IS_WIDGET
index f358f8cbbd6a9be8390d5e2d9e0d4e350b36314b..33951ce463168e776d43cfab5a19a4ba24f076a4 100644 (file)
@@ -72,6 +72,8 @@ struct _GtkActionMuxer
   GtkActionMuxer *parent;
 
   GtkWidget *widget;
+  GPtrArray *widget_actions;
+  gboolean *widget_actions_enabled;
 };
 
 G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT,
@@ -83,6 +85,7 @@ enum
   PROP_0,
   PROP_PARENT,
   PROP_WIDGET,
+  PROP_WIDGET_ACTIONS,
   NUM_PROPERTIES
 };
 
@@ -108,7 +111,7 @@ typedef struct
 static void
 gtk_action_muxer_append_group_actions (const char *prefix,
                                        Group      *group,
-                                       GArray     *actions)
+                                       GHashTable *actions)
 {
   gchar **group_actions;
   gchar **action;
@@ -116,10 +119,8 @@ gtk_action_muxer_append_group_actions (const char *prefix,
   group_actions = g_action_group_list_actions (group->group);
   for (action = group_actions; *action; action++)
     {
-      gchar *fullname;
-
-      fullname = g_strconcat (prefix, ".", *action, NULL);
-      g_array_append_val (actions, fullname);
+      char *name = g_strconcat (prefix, ".", *action, NULL);
+      g_hash_table_add (actions, name);
     }
 
   g_strfreev (group_actions);
@@ -129,9 +130,11 @@ static gchar **
 gtk_action_muxer_list_actions (GActionGroup *action_group)
 {
   GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
-  GArray *actions;
+  GHashTable *actions;
+  char **keys;
 
-  actions = g_array_new (TRUE, FALSE, sizeof (gchar *));
+  actions = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                   g_free, NULL);
 
   for ( ; muxer != NULL; muxer = muxer->parent)
     {
@@ -139,12 +142,28 @@ gtk_action_muxer_list_actions (GActionGroup *action_group)
       const char *prefix;
       Group *group;
 
+      if (muxer->widget_actions)
+        {
+          int i;
+
+          for (i = 0; i < muxer->widget_actions->len; i++)
+            {
+              GtkWidgetAction *action = g_ptr_array_index (muxer->widget_actions, i);
+              g_hash_table_add (actions, g_strdup (action->name));
+            }
+        }
+
       g_hash_table_iter_init (&iter, muxer->groups);
       while (g_hash_table_iter_next (&iter, (gpointer *)&prefix, (gpointer *)&group))
         gtk_action_muxer_append_group_actions (prefix, group, actions);
     }
 
-  return (gchar **)(void *) g_array_free (actions, FALSE);
+  keys = (char **)g_hash_table_get_keys_as_array (actions, NULL);
+
+  g_hash_table_steal_all (actions);
+  g_hash_table_unref (actions);
+
+  return (char **)keys;
 }
 
 static Group *
@@ -183,7 +202,7 @@ gtk_action_muxer_find (GtkActionMuxer  *muxer,
   return group->group;
 }
 
-static void
+void
 gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer,
                                          const gchar    *action_name,
                                          gboolean        enabled)
@@ -191,6 +210,19 @@ gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer,
   Action *action;
   GSList *node;
 
+  if (muxer->widget_actions)
+    {
+      int i;
+      for (i = 0; i < muxer->widget_actions->len; i++)
+        {
+          GtkWidgetAction *a = g_ptr_array_index (muxer->widget_actions, i);
+          if (strcmp (a->name, action_name) == 0)
+            {
+              muxer->widget_actions_enabled[i] = enabled;
+              break;
+            }
+        }
+    }
   action = g_hash_table_lookup (muxer->observed_actions, action_name);
   for (node = action ? action->watchers : NULL; node; node = node->next)
     gtk_action_observer_action_enabled_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, enabled);
@@ -223,7 +255,7 @@ gtk_action_muxer_parent_action_enabled_changed (GActionGroup *action_group,
   gtk_action_muxer_action_enabled_changed (muxer, action_name, enabled);
 }
 
-static void
+void
 gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer,
                                        const gchar    *action_name,
                                        GVariant       *state)
@@ -401,6 +433,46 @@ gtk_action_muxer_query_action (GActionGroup        *action_group,
   Group *group;
   const gchar *unprefixed_name;
 
+  if (muxer->widget_actions)
+    {
+      int i;
+
+      for (i = 0; i < muxer->widget_actions->len; i++)
+        {
+          GtkWidgetAction *action = g_ptr_array_index (muxer->widget_actions, i);
+          if (strcmp (action->name, action_name) == 0)
+            {
+              if (enabled)
+                *enabled = muxer->widget_actions_enabled[i];
+              if (parameter_type)
+                *parameter_type = action->parameter_type;
+
+              if (state_hint)
+                *state_hint = NULL;
+              if (state_type)
+                *state_type = NULL;
+              if (state)
+                *state = NULL;
+
+              if (action->get_state)
+                {
+                  GVariant *s;
+
+                  s = g_variant_ref_sink (action->get_state (muxer->widget, action->name));
+
+                  if (state_type)
+                    *state_type = g_variant_get_type (s);
+                  if (state)
+                    *state = g_variant_ref (s);
+
+                  g_variant_unref (s);
+                }
+
+              return TRUE;
+            }
+       }
+    }
+
   group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
 
   if (group)
@@ -424,6 +496,22 @@ gtk_action_muxer_activate_action (GActionGroup *action_group,
   Group *group;
   const gchar *unprefixed_name;
 
+  if (muxer->widget_actions)
+    {
+      int i;
+
+      for (i = 0; i < muxer->widget_actions->len; i++)
+        {
+          GtkWidgetAction *action = g_ptr_array_index (muxer->widget_actions, i);
+          if (strcmp (action->name, action_name) == 0)
+            {
+              action->activate (muxer->widget, action->name, parameter);
+
+              return;
+            }
+        }
+    }
+
   group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
 
   if (group)
@@ -441,6 +529,23 @@ gtk_action_muxer_change_action_state (GActionGroup *action_group,
   Group *group;
   const gchar *unprefixed_name;
 
+  if (muxer->widget_actions)
+    {
+      int i;
+
+      for (i = 0; i < muxer->widget_actions->len; i++)
+        {
+          GtkWidgetAction *action = g_ptr_array_index (muxer->widget_actions, i);
+          if (strcmp (action->name, action_name) == 0)
+            {
+              if (action->set_state)
+                action->set_state (muxer->widget, action->name, state);
+
+              return;
+            }
+        }
+    }
+
   group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
 
   if (group)
@@ -600,6 +705,10 @@ gtk_action_muxer_get_property (GObject    *object,
       g_value_set_object (value, muxer->widget);
       break;
 
+    case PROP_WIDGET_ACTIONS:
+      g_value_set_boxed (value, muxer->widget_actions);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
@@ -623,6 +732,18 @@ gtk_action_muxer_set_property (GObject      *object,
       muxer->widget = g_value_get_object (value);
       break;
 
+    case PROP_WIDGET_ACTIONS:
+      muxer->widget_actions = g_value_get_boxed (value);
+      if (muxer->widget_actions)
+        {
+          int i;
+
+          muxer->widget_actions_enabled = g_new (gboolean, muxer->widget_actions->len);
+          for (i = 0; i < muxer->widget_actions->len; i++)
+            muxer->widget_actions_enabled[i] = TRUE;
+        }
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
@@ -683,6 +804,13 @@ gtk_action_muxer_class_init (GObjectClass *class)
                                                  G_PARAM_CONSTRUCT_ONLY |
                                                  G_PARAM_STATIC_STRINGS);
 
+  properties[PROP_WIDGET_ACTIONS] = g_param_spec_boxed ("widget-actions", "Widget actions",
+                                                        "Widget actions",
+                                                        G_TYPE_PTR_ARRAY,
+                                                        G_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_STRINGS);
+
   g_object_class_install_properties (class, NUM_PROPERTIES, properties);
 }
 
@@ -791,14 +919,17 @@ gtk_action_muxer_lookup (GtkActionMuxer *muxer,
 /*< private >
  * gtk_action_muxer_new:
  * @widget: the widget to which the muxer belongs
+ * @actions: widget actions
  *
  * Creates a new #GtkActionMuxer.
  */
 GtkActionMuxer *
-gtk_action_muxer_new (GtkWidget *widget)
+gtk_action_muxer_new (GtkWidget *widget,
+                      GPtrArray *actions)
 {
   return g_object_new (GTK_TYPE_ACTION_MUXER,
                        "widget", widget,
+                       "widget-actions", actions,
                        NULL);
 }
 
index 190728fafddacb8ca25af0e784e84900d8905029..325c1f817a48f432924574b1f5956ee3513027d1 100644 (file)
@@ -31,10 +31,21 @@ G_BEGIN_DECLS
 #define GTK_IS_ACTION_MUXER(inst)                           (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
                                                              GTK_TYPE_ACTION_MUXER))
 
+typedef struct {
+  char *name;
+
+  GVariantType *parameter_type;
+
+  GtkWidgetActionActivateFunc activate;
+  GtkWidgetActionSetStateFunc set_state;
+  GtkWidgetActionGetStateFunc get_state;
+} GtkWidgetAction;
+
 typedef struct _GtkActionMuxer                              GtkActionMuxer;
 
 GType                   gtk_action_muxer_get_type                       (void);
-GtkActionMuxer *        gtk_action_muxer_new                            (GtkWidget      *widget);
+GtkActionMuxer *        gtk_action_muxer_new                            (GtkWidget      *widget,
+                                                                         GPtrArray      *actions);
 
 void                    gtk_action_muxer_insert                         (GtkActionMuxer *muxer,
                                                                          const gchar    *prefix,
@@ -59,6 +70,16 @@ void                    gtk_action_muxer_set_primary_accel              (GtkActi
 const gchar *           gtk_action_muxer_get_primary_accel              (GtkActionMuxer *muxer,
                                                                          const gchar    *action_and_target);
 
+void
+gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer,
+                                         const char     *action_name,
+                                         gboolean        enabled);
+void
+gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer,
+                                       const gchar    *action_name,
+                                       GVariant       *state);
+
+
 /* No better place for these... */
 gchar *                 gtk_print_action_and_target                     (const gchar    *action_namespace,
                                                                          const gchar    *action_name,
index f90fd827fcb3d0eb2ddbc2fd19f87a52dcfb9186..3b3fe1e5ac431b00b7bd6203ba2ff3a91c086fb8 100644 (file)
@@ -394,7 +394,7 @@ gtk_application_init (GtkApplication *application)
 {
   GtkApplicationPrivate *priv = gtk_application_get_instance_private (application);
 
-  priv->muxer = gtk_action_muxer_new (NULL);
+  priv->muxer = gtk_action_muxer_new (NULL, NULL);
 
   priv->accels = gtk_application_accels_new ();
 }
index 444ecbe4ddc6601f451592d40bd56a644a5966f5..f2a8c9710c5e374442a1636cad56750f744c5788 100644 (file)
@@ -501,6 +501,7 @@ struct _GtkWidgetClassPrivate
   AtkRole accessible_role;
   const char *css_name;
   GType layout_manager_type;
+  GPtrArray *actions;
 };
 
 enum {
@@ -11897,14 +11898,16 @@ _gtk_widget_get_action_muxer (GtkWidget *widget,
                               gboolean   create)
 {
   GtkActionMuxer *muxer;
+  GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS (widget);
+  GtkWidgetClassPrivate *priv = widget_class->priv;
 
   muxer = (GtkActionMuxer*)g_object_get_qdata (G_OBJECT (widget), quark_action_muxer);
   if (muxer)
     return muxer;
 
-  if (create)
+  if (create || priv->actions)
     {
-      muxer = gtk_action_muxer_new (widget);
+      muxer = gtk_action_muxer_new (widget, priv->actions);
       g_object_set_qdata_full (G_OBJECT (widget),
                                quark_action_muxer,
                                muxer,
@@ -13427,3 +13430,133 @@ gtk_widget_should_layout (GtkWidget *widget)
   return TRUE;
 }
 
+/*
+ * gtk_widget_class_install_action:
+ * @widget_class: a #GtkWidgetClass
+ * @action_name: a prefixed action name, such as "clipboard.paste"
+ * @activate: callback to use when the action is activated
+ *
+ * This should be called at class initialization time to specify
+ * actions to be added for all instances of this class.
+ *
+ * Actions installed by this function are stateless. The only state
+ * they have is whether they are enabled or not. For more complicated
+ * actions, see gtk_widget_class_install_stateful_action().
+ */
+void
+gtk_widget_class_install_action (GtkWidgetClass              *widget_class,
+                                 const char                  *action_name,
+                                 GtkWidgetActionActivateFunc  activate)
+{
+  gtk_widget_class_install_stateful_action (widget_class, action_name, activate,
+                                            NULL, NULL, NULL);
+}
+
+/*
+ * gtk_widget_class_install_stateful_action:
+ * @widget_class: a #GtkWidgetClass
+ * @action_name: a prefixed action name, such as "clipboard.paste"
+ * @activate: callback to use when the action is activated
+ * @parameter_type: (allow-none): the parameter type, or %NULL
+ * @query: (allow-none): callback to use when the action properties
+       are queried, or %NULL for always-enabled stateless actions
+ * @query_state: (allow-none): callback to use when the action state
+       is queried, or %NULL for stateless actions
+ *
+ * This should be called at class initialization time to specify
+ * actions to be added for all instances of this class.
+ *
+ * Actions installed in this way can be simple or stateful.
+ * See the #GAction documentation for more information.
+ */
+void
+gtk_widget_class_install_stateful_action (GtkWidgetClass              *widget_class,
+                                          const char                  *action_name,
+                                          GtkWidgetActionActivateFunc  activate,
+                                          const char                  *parameter_type,
+                                          GtkWidgetActionSetStateFunc  set_state,
+                                          GtkWidgetActionGetStateFunc  get_state)
+{
+  GtkWidgetClassPrivate *priv = widget_class->priv;
+  GtkWidgetAction *action;
+
+  g_return_if_fail (GTK_IS_WIDGET_CLASS (widget_class));
+
+  if (priv->actions == NULL)
+    priv->actions = g_ptr_array_new ();
+  else if (GTK_IS_WIDGET_CLASS (&widget_class->parent_class))
+    {
+      GtkWidgetClass *parent_class = GTK_WIDGET_CLASS (&widget_class->parent_class);
+      GtkWidgetClassPrivate *parent_priv = parent_class->priv;
+      GPtrArray *parent_actions = parent_priv->actions;
+
+      if (priv->actions == parent_actions)
+        {
+          int i;
+
+          priv->actions = g_ptr_array_new ();
+          for (i = 0; i < parent_actions->len; i++)
+            g_ptr_array_add (priv->actions, g_ptr_array_index (parent_actions, i));
+        }
+    }
+
+  action = g_new0 (GtkWidgetAction, 1);
+  action->name = g_strdup (action_name);
+  action->activate = activate;
+  action->parameter_type = parameter_type ? g_variant_type_new (parameter_type) : NULL;
+  action->set_state = set_state;
+  action->get_state = get_state;
+
+  GTK_NOTE(ACTIONS,
+           g_message ("%sClass: Adding %s action\n",
+                      g_type_name (G_TYPE_FROM_CLASS (widget_class)),
+                      action_name));
+
+  g_ptr_array_add (priv->actions, action);
+}
+
+/**
+ * gtk_widget_action_enabled_changed:
+ * @widget: a #GtkWidget
+ * @action_name: action name, such as "clipboard.paste"
+ * @enabled: whether the action is now enabled
+ *
+ * Notify when an action installed with
+ * gtk_widget_class_install_action() changes its
+ * enabled state.
+ */
+void
+gtk_widget_action_enabled_changed (GtkWidget  *widget,
+                                   const char *action_name,
+                                   gboolean    enabled)
+{
+  GtkActionMuxer *muxer;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  muxer = _gtk_widget_get_action_muxer (widget, TRUE);
+  gtk_action_muxer_action_enabled_changed (muxer, action_name, enabled);
+}
+
+/**
+ * gtk_widget_action_state_changed:
+ * @widget: a #GtkWidget
+ * @action_name: action name, such as "clipboard.paste"
+ * @state: the new state
+ *
+ * Notify when an action installed with
+ * gtk_widget_class_install_stateful_action() changes
+ * its state.
+ */
+void
+gtk_widget_action_state_changed (GtkWidget  *widget,
+                                 const char *action_name,
+                                 GVariant   *state)
+{
+  GtkActionMuxer *muxer;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  muxer = _gtk_widget_get_action_muxer (widget, TRUE);
+  gtk_action_muxer_action_state_changed (muxer, action_name, state);
+}
index ca9d768ea6dbeb4e0f65d49b6a16954514304071..73e92ac1cbf507dadaed22b52f6528f0d55d797b 100644 (file)
@@ -1023,6 +1023,76 @@ GDK_AVAILABLE_IN_ALL
 gboolean                gtk_widget_should_layout        (GtkWidget   *widget);
 
 
+/**
+ * GtkWidgetActionActivateFunc:
+ * @widget: the widget to which the action belongs
+ * @action_name: the action name
+ * @parameter: parameter for activation
+ *
+ * The type of the callback functions used for activating
+ * actions installed with gtk_widget_class_install_action().
+ *
+ * The @parameter must match the @parameter_type of the action.
+ */
+typedef void (* GtkWidgetActionActivateFunc) (GtkWidget  *widget,
+                                              const char *action_name,
+                                              GVariant   *parameter);
+
+/**
+ * GtkWidgetActionGetStateFunc:
+ * @widget: the widget to which the action belongs
+ * @action_name: the action name
+ *
+ * The type of the callback functions used to query the state
+ * of stateful actions installed with gtk_widget_class_install_action().
+ *
+ * See the #GAction documentation for more details about the
+ * meaning of these properties.
+ */
+typedef GVariant * (* GtkWidgetActionGetStateFunc) (GtkWidget  *widget,
+                                                    const char *action_name);
+
+/**
+ * GtkWidgetActionSetStateFunc:
+ * @widget: the widget to which the action belongs
+ * @action_name: the action name
+ * @state: the new state
+ *
+ * The type of the callback functions used to change the
+ * state of actions installed with gtk_widget_class_install_action().
+ *
+ * The @state must match the @state_type of the action.
+ *
+ * This callback is used when the action state is
+ * changed via the #GActionGroup API.
+ */
+typedef void (*GtkWidgetActionSetStateFunc) (GtkWidget  *widget,
+                                             const char *action_name,
+                                             GVariant   *state);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_widget_class_install_action (GtkWidgetClass              *widget_class,
+                                                         const char                  *action_name,
+                                                         GtkWidgetActionActivateFunc  activate);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_widget_class_install_stateful_action (GtkWidgetClass              *widget_class,
+                                                                  const char                  *action_name,
+                                                                  GtkWidgetActionActivateFunc  activate,
+                                                                  const char                  *parameter_type,
+                                                                  GtkWidgetActionSetStateFunc  set_state,
+                                                                  GtkWidgetActionGetStateFunc  get_state);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_widget_action_enabled_changed (GtkWidget  *widget,
+                                                           const char *action_name,
+                                                           gboolean    enabled);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_widget_action_state_changed   (GtkWidget  *widget,
+                                                           const char *action_name,
+                                                           GVariant   *state);
+
+
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkWidget, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkRequisition, gtk_requisition_free)